/*
 * BlackLightning.PluginLib.Components.LoginClient
 * Andy Tidball
 * 
 * Project: Black Lightning Plugin Library
 * Copyright: GNU General Public License
 */

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.Remoting.Messaging;
using System.Windows.Forms;
using BlackLightning.PluginLib;
using BlackLightning.PluginLib.Properties;
using BLLib.Net;

namespace BlackLightning.PluginLib.LoginClients {
	/// <summary>
	/// Provides all the base functionality for a derived class to communicate with Simutronics' login server.
	/// </summary>
	public abstract class LoginClient : Form, ILoginClient {
		/// <summary>
		/// The DNS host where Simutronics' login server is running.
		/// </summary>
		public const string LoginServerHost = "eaccess.play.net";

		/// <summary>
		/// The port to connect to LoginServerHost at.
		/// </summary>
		public const int LoginServerPort = 7900;

		/// <summary>
		/// The URL of Simutronics' policy.
		/// </summary>
		public const string TermsURL = "http://www.play.net/playdotnet/simu_policy.asp";

		// member data
		private Connection             _Connection = null;  // the connection to the login server
		private List<BackgroundWorker> _Workers    = null;  // a list of currently executing BackgroundWorkers

		/// <summary>
		/// Creates a new LoginClient.
		/// </summary>
		protected LoginClient() {
			_Connection = new Connection();
			_Connection.Blocking = true;
			_Workers = new List<BackgroundWorker>();
			Icon = Resources.BlackLightningIcon;
			//Icon = new Icon(typeof(BLSGE).Assembly.GetManifestResourceStream("BlackLightning.PluginLib.Resources.BlackLightning.ico"));
			//Icon = new Icon("BlackLightning.ico");
			//Icon = ((Icon)(Resources.GetObject("Icon")));
		}

		/// <summary>
		/// Gets whether or not the client is currently connected to the login server.
		/// </summary>
		protected bool Connected {
			get {
				return _Connection.Connected;
			}
		}

		/// <summary>
		/// Connects to the login server.
		/// </summary>
		protected void Connect() {
			_Connection.Connect(LoginServerHost, LoginServerPort);
		}

		/// <summary>
		/// Used by BeginConnect and EndConnect.
		/// </summary>
		private delegate void ConnectDelegate();

		/// <summary>
		/// Starts an asynchronous call to Connect.
		/// </summary>
		/// <param name="Callback">A delegate to invoke when the asynchronous call completes.</param>
		/// <param name="AsyncState">State information to pass to the matching End call.</param>
		/// <returns>An IAsyncResult that can be used to monitor the asynchronous call.</returns>
		protected IAsyncResult BeginConnect(AsyncCallback Callback, object AsyncState) {
			return new ConnectDelegate(Connect).BeginInvoke(Callback, AsyncState);
		}

		/// <summary>
		/// Ends an asynchronous call to Connect.
		/// </summary>
		/// <param name="Result">The return value from the matching Begin call.</param>
		protected void EndConnect(IAsyncResult Result) {
			((Result as AsyncResult).AsyncDelegate as ConnectDelegate).EndInvoke(Result);
		}

		/// <summary>
		/// Starts a background call to Connect.  ConnectCompleted will be fired on completion.
		/// </summary>
		protected void BackgroundConnect() {
			BackgroundWorker BW = new BackgroundWorker();
			_Workers.Add(BW);
			BW.DoWork             += DoBackgroundConnect;
			BW.RunWorkerCompleted += OnConnectCompleted;
			BW.WorkerReportsProgress      = false;
			BW.WorkerSupportsCancellation = true;
			BW.RunWorkerAsync();
		}

		/// <summary>
		/// Fired when a call to BackgroundConnect completes.
		/// </summary>
		protected event RunWorkerCompletedEventHandler ConnectCompleted;

		/// <summary>
		/// Handles the DoWork event for the BackgroundConnect worker.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work to be done.</param>
		private void DoBackgroundConnect(object Sender, DoWorkEventArgs e) {
			Connect();
		}

		/// <summary>
		/// Fires the ConnectCompleted event.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work which has completed.</param>
		private void OnConnectCompleted(object Sender, RunWorkerCompletedEventArgs e) {
			if (ConnectCompleted != null) {
				ConnectCompleted(this, e);
			}
			_Workers.Remove(Sender as BackgroundWorker);
		}

		/// <summary>
		/// Sends an arbitrary command to the server and returns its response.
		/// </summary>
		/// <param name="Params">A list of strings to be sent to the server in a tab-delimited format.</param>
		/// <returns>The response from the server, split along tabs.</returns>
		protected string[] SendCommand(params string[] Params) {
			// join the parameters with tabs and end it off with a newline
			string Data = string.Join("\t", Params) + "\n";

			// send the data to the server
			_Connection.Send(Data);

			// receive the response
			Data = _Connection.Receive();

			// trim off the trailing newline
			Data = Data.Substring(0, Data.Length-1);

			// split the data across tabs and return it
			return Data.Split('\t');
		}

		/// <summary>
		/// Sends the "K" command to receive an encryption key for the user's password.
		/// </summary>
		/// <returns>The encryption key sent by the login server.</returns>
		protected string GetEncryptionKey() {
			string[] Value = SendCommand("K");
			return Value[0];
		}

		/// <summary>
		/// Used by BeginGetEncryptionKey and EndGetEncryptionKey.
		/// </summary>
		private delegate string GetEncryptionKeyDelegate();

		/// <summary>
		/// Starts an asynchronous call to GetEncryptionKey.
		/// </summary>
		/// <param name="Callback">A delegate to invoke when the asynchronous call completes.</param>
		/// <param name="AsyncState">State information to pass to the matching End call.</param>
		/// <returns>An IAsyncResult that can be used to monitor the asynchronous call.</returns>
		protected IAsyncResult BeginGetEncryptionKey(AsyncCallback Callback, object AsyncState) {
			return new GetEncryptionKeyDelegate(GetEncryptionKey).BeginInvoke(Callback, AsyncState);
		}

		/// <summary>
		/// Ends an asynchronous call to GetEncryptionKey.
		/// </summary>
		/// <param name="Result">The return value from the matching Begin call.</param>
		/// <returns>The encryption key sent by the login server.</returns>
		protected string EndGetEncryptionKey(IAsyncResult Result) {
			return ((Result as AsyncResult).AsyncDelegate as GetEncryptionKeyDelegate).EndInvoke(Result);
		}

		/// <summary>
		/// Starts a background call to GetEncryptionKey.  GetEncryptionKeyCompleted will be fired on completion.
		/// </summary>
		protected void BackgroundGetEncryptionKey() {
			BackgroundWorker BW = new BackgroundWorker();
			_Workers.Add(BW);
			BW.DoWork             += DoBackgroundGetEncryptionKey;
			BW.RunWorkerCompleted += OnGetEncryptionKeyCompleted;
			BW.WorkerReportsProgress      = false;
			BW.WorkerSupportsCancellation = true;
			BW.RunWorkerAsync();
		}

		/// <summary>
		/// Fired when a call to GetEncryptionKey completes.
		/// </summary>
		protected event RunWorkerCompletedEventHandler GetEncryptionKeyCompleted;

		/// <summary>
		/// Handles the DoWork event for the BackgroundGetEncryptionKey worker.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work to be done.</param>
		private void DoBackgroundGetEncryptionKey(object Sender, DoWorkEventArgs e) {
			e.Result = GetEncryptionKey();
		}

		/// <summary>
		/// Fires the GetEncryptionKeyCompleted event.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work which has completed.</param>
		private void OnGetEncryptionKeyCompleted(object Sender, RunWorkerCompletedEventArgs e) {
			if (GetEncryptionKeyCompleted != null) {
				GetEncryptionKeyCompleted(this, e);
			}
			_Workers.Remove(Sender as BackgroundWorker);
		}

		/// <summary>
		/// Encrypts the given password using the given key (which should be retrieved using GetEncryptionKey).
		/// </summary>
		/// <param name="Password">The password to encrypt.</param>
		/// <param name="Key">The key to use to encrypt the password.</param>
		/// <returns>The encrypted password.</returns>
		protected string EncryptPassword(string Password, string Key) {
			char[] Output = new char[Password.Length];
			for (int i=0; i<Password.Length; ++i) {
				Output[i] = (char)(Password[i] ^ Key[i]);
				if (Key[i] >= (char)96) {
					Output[i] += (char)64;
				}
			}
			return new string(Output);
		}

		/// <summary>
		/// Sends the "A" command to authenticate the given username and encrypted password with the login server.
		/// </summary>
		/// <param name="Username">The username to authenticate.</param>
		/// <param name="EncryptedPassword">The password to authenticate after having been encrypted with EncryptPassword.</param>
		/// <returns>A dictionary containing the following keys: "key", "owner".</returns>
		/// <exception cref="System.Security.Authentication.AuthenticationException">Thrown if authentication fails.</exception>
		protected Dictionary<string, string> Authenticate(string Username, string EncryptedPassword) {
			string[] Values = SendCommand("A", Username, EncryptedPassword);
			switch (Values[2]) {
				case "NORECORD":
					throw new System.Security.Authentication.AuthenticationException("Unknown username: " + Username);
				case "PASSWORD":
					throw new System.Security.Authentication.AuthenticationException("Invalid password for username: " + Username);
				case "KEY": {
					Dictionary<string, string> ReturnValue = new Dictionary<string, string>(2);
					ReturnValue.Add("key", Values[3]);
					ReturnValue.Add("owner", Values[4]);
					return ReturnValue;
				}
				default:
					throw new System.Security.Authentication.AuthenticationException("Unknown error during authentication");
			}
		}

		/// <summary>
		/// Used by BeginAuthenticate and EndAuthenticate.
		/// </summary>
		private delegate Dictionary<string, string> AuthenticateDelegate(string Username, string EncryptedPassword);

		/// <summary>
		/// Starts an asynchronous call to Authenticate.
		/// </summary>
		/// <param name="Username">The username to authenticate.</param>
		/// <param name="EncryptedPassword">The password to authenticate after having been encrypted with EncryptPassword.</param>
		/// <param name="Callback">A delegate to invoke when the asynchronous call completes.</param>
		/// <param name="AsyncState">State information to pass to the matching End call.</param>
		/// <returns>An IAsyncResult that can be used to monitor the asynchronous call.</returns>
		protected IAsyncResult BeginAuthenticate(string Username, string EncryptedPassword, AsyncCallback Callback, object AsyncState) {
			return new AuthenticateDelegate(Authenticate).BeginInvoke(Username, EncryptedPassword, Callback, AsyncState);
		}

		/// <summary>
		/// Ends an asynchronous call to Authenticate.
		/// </summary>
		/// <param name="Result">The return value from the matching Begin call.</param>
		/// <returns>A dictionary containing the following keys: "key", "owner".</returns>
		protected Dictionary<string, string> EndAuthenticate(IAsyncResult Result) {
			return ((Result as AsyncResult).AsyncDelegate as AuthenticateDelegate).EndInvoke(Result);
		}

		/// <summary>
		/// Starts a background call to Authenticate.  AuthenticateCompleted will be fired on completion.
		/// </summary>
		/// <param name="Username">The username to authenticate.</param>
		/// <param name="EncryptedPassword">The password to authenticate after having been encrypted with EncryptPassword.</param>
		protected void BackgroundAuthenticate(string Username, string EncryptedPassword) {
			BackgroundWorker BW = new BackgroundWorker();
			_Workers.Add(BW);
			BW.DoWork             += DoBackgroundAuthenticate;
			BW.RunWorkerCompleted += OnAuthenticateCompleted;
			BW.WorkerReportsProgress      = false;
			BW.WorkerSupportsCancellation = true;
			BW.RunWorkerAsync(new object[]{Username, EncryptedPassword});
		}

		/// <summary>
		/// Fired when a call to BackgroundAuthenticate completes.
		/// </summary>
		protected event RunWorkerCompletedEventHandler AuthenticateCompleted;

		/// <summary>
		/// Handles the DoWork event for the BackgroundAuthenticate worker.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work to be done.</param>
		private void DoBackgroundAuthenticate(object Sender, DoWorkEventArgs e) {
			object[] Args = e.Argument as object[];
			e.Result = Authenticate(Args[0] as string, Args[1] as string);
		}

		/// <summary>
		/// Fires the AuthenticateCompleted event.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work which has completed.</param>
		private void OnAuthenticateCompleted(object Sender, RunWorkerCompletedEventArgs e) {
			if (AuthenticateCompleted != null) {
				AuthenticateCompleted(this, e);
			}
			_Workers.Remove(Sender as BackgroundWorker);
		}

		/// <summary>
		/// Encapsulates calls to GetEncryptKey, EncryptPassword, and Authenticate into a single method.
		/// </summary>
		/// <param name="Username">The username to login with.</param>
		/// <param name="Password">The password (unencrypted) to login with.</param>
		/// <returns>The return value of Authenticate.</returns>
		/// <exception cref="System.Security.Authentication.AuthenticationException">Thrown if authentication fails.</exception>
		protected Dictionary<string, string> Login(string Username, string Password) {
			return Authenticate(Username, EncryptPassword(Password, GetEncryptionKey()));
		}

		/// <summary>
		/// Used by BeginLogin and EndLogin.
		/// </summary>
		private delegate Dictionary<string, string> LoginDelegate(string Username, string Password);

		/// <summary>
		/// Starts an asynchronous call to Login.
		/// </summary>
		/// <param name="Username">The username to login with.</param>
		/// <param name="Password">The password (unencrypted) to login with.</param>
		/// <param name="Callback">A delegate to invoke when the asynchronous call completes.</param>
		/// <param name="AsyncState">State information to pass to the matching End call.</param>
		/// <returns>An IAsyncResult that can be used to monitor the asynchronous call.</returns>
		protected IAsyncResult BeginLogin(string Username, string Password, AsyncCallback Callback, object AsyncState) {
			return new LoginDelegate(Login).BeginInvoke(Username, Password, Callback, AsyncState);
		}

		/// <summary>
		/// Ends an asynchronous call to Login.
		/// </summary>
		/// <param name="Result">The return value from the matching Begin call.</param>
		/// <returns>The return value of Authenticate.</returns>
		protected Dictionary<string, string> EndLogin(IAsyncResult Result) {
			return ((Result as AsyncResult).AsyncDelegate as LoginDelegate).EndInvoke(Result);
		}

		/// <summary>
		/// Starts a background call to Login.  LoginCompleted will be fired on completion.
		/// </summary>
		/// <param name="Username">The username to login with.</param>
		/// <param name="Password">The password (unencrypted) to login with.</param>
		protected void BackgroundLogin(string Username, string Password) {
			BackgroundWorker BW = new BackgroundWorker();
			_Workers.Add(BW);
			BW.DoWork             += DoBackgroundLogin;
			BW.RunWorkerCompleted += OnLoginCompleted;
			BW.WorkerReportsProgress      = false;
			BW.WorkerSupportsCancellation = true;
			BW.RunWorkerAsync(new object[]{Username, Password});
		}

		/// <summary>
		/// Fired when a call to BackgroundLogin completes.
		/// </summary>
		protected event RunWorkerCompletedEventHandler LoginCompleted;

		/// <summary>
		/// Handles the DoWork event for the BackgroundLogin worker.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work to be done.</param>
		private void DoBackgroundLogin(object Sender, DoWorkEventArgs e) {
			object[] Args = e.Argument as object[];
			e.Result = Login(Args[0] as string, Args[1] as string);
		}

		/// <summary>
		/// Fires the LoginCompleted event.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work which has completed.</param>
		private void OnLoginCompleted(object Sender, RunWorkerCompletedEventArgs e) {
			if (LoginCompleted != null) {
				LoginCompleted(this, e);
			}
			_Workers.Remove(Sender as BackgroundWorker);
		}

		/// <summary>
		/// Sends the "M" command to the server to retrieve a list of games.
		/// </summary>
		/// <returns>A list of GameInfo structs, each containing a game's code and name.</returns>
		protected List<GameInfo> GetGameList() {
			// grab the list of games from the server
			string[] Values = SendCommand("M");

			// loop over them and create a list of GameInfo structs
			List<GameInfo> Games = new List<GameInfo>((Values.Length-1)/2);
			for (int i=1; i<Values.Length; i+=2) {
				Games.Add(new GameInfo(Values[i], Values[i+1]));
			}

			// return the list
			return Games;
		}

		/// <summary>
		/// Used by BeginGetGameList and EndGetGameList.
		/// </summary>
		private delegate List<GameInfo> GetGameListDelegate();

		/// <summary>
		/// Starts an asynchronous call to GetGameList.
		/// </summary>
		/// <param name="Callback">A delegate to invoke when the asynchronous call completes.</param>
		/// <param name="AsyncState">State information to pass to the matching End call.</param>
		/// <returns>An IAsyncResult that can be used to monitor the asynchronous call.</returns>
		protected IAsyncResult BeginGetGameList(AsyncCallback Callback, object AsyncState) {
			return new GetGameListDelegate(GetGameList).BeginInvoke(Callback, AsyncState);
		}

		/// <summary>
		/// Ends an asynchronous call to GetGameList.
		/// </summary>
		/// <param name="Result">The return value from the matching Begin call.</param>
		/// <returns>A list of GameInfo structs, each containing a game's code and name.</returns>
		protected List<GameInfo> EndGetGameList(IAsyncResult Result) {
			return ((Result as AsyncResult).AsyncDelegate as GetGameListDelegate).EndInvoke(Result);
		}

		/// <summary>
		/// Starts a background call to GetGameList.  GetGameListCompleted will be fired on completion.
		/// </summary>
		protected void BackgroundGetGameList() {
			BackgroundWorker BW = new BackgroundWorker();
			_Workers.Add(BW);
			BW.DoWork             += DoBackgroundGetGameList;
			BW.RunWorkerCompleted += OnGetGameListCompleted;
			BW.WorkerReportsProgress      = false;
			BW.WorkerSupportsCancellation = true;
			BW.RunWorkerAsync();
		}

		/// <summary>
		/// Fired when a call to BackgroundGetGameList completes.
		/// </summary>
		protected event RunWorkerCompletedEventHandler GetGameListCompleted;

		/// <summary>
		/// Handles the DoWork event for the BackgroundGetGameList worker.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work to be done.</param>
		private void DoBackgroundGetGameList(object Sender, DoWorkEventArgs e) {
			e.Result = GetGameList();
		}

		/// <summary>
		/// Fires the GetGameListCompleted event.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work which has completed.</param>
		private void OnGetGameListCompleted(object Sender, RunWorkerCompletedEventArgs e) {
			if (GetGameListCompleted != null) {
				GetGameListCompleted(this, e);
			}
			_Workers.Remove(Sender as BackgroundWorker);
		}

		/// <summary>
		/// Uses the "N" command to check the game type for a given game.
		/// </summary>
		/// <param name="GameCode">The code of the game whose type to check.</param>
		/// <returns>The GameType for the specified game code.</returns>
		protected GameType GetGameType(string GameCode) {
			// send the command to the server
			string[] Values = SendCommand("N", GameCode);

			// check the results
			if (Values[1].IndexOf("PRODUCTION") >= 0) {
				return GameType.Production;
			} else if (Values[1].IndexOf("DEVELOPMENT") >= 0) {
				return GameType.Development;
			} else {
				throw new ArgumentException("Unknown game code: " + Values[1]);
			}
		}

		/// <summary>
		/// Used by BeginGetGameType and EndGetGameType.
		/// </summary>
		private delegate GameType GetGameTypeDelegate(string GameCode);

		/// <summary>
		/// Starts an asynchronous call to GetGameType.
		/// </summary>
		/// <param name="GameCode">The code of the game whose type to check.</param>
		/// <param name="Callback">A delegate to invoke when the asynchronous call completes.</param>
		/// <param name="AsyncState">State information to pass to the matching End call.</param>
		/// <returns>An IAsyncResult that can be used to monitor the asynchronous call.</returns>
		protected IAsyncResult BeginGetGameType(string GameCode, AsyncCallback Callback, object AsyncState) {
			return new GetGameTypeDelegate(GetGameType).BeginInvoke(GameCode, Callback, AsyncState);
		}

		/// <summary>
		/// Ends an asynchronous call to GetGameType.
		/// </summary>
		/// <param name="Result">The return value from the matching Begin call.</param>
		/// <returns>The GameType for the specified game code.</returns>
		protected GameType EndGetGameType(IAsyncResult Result) {
			return ((Result as AsyncResult).AsyncDelegate as GetGameTypeDelegate).EndInvoke(Result);
		}

		/// <summary>
		/// Starts a background call to GetGameType.  GetGameTypeCompleted will be fired on completion.
		/// </summary>
		/// <param name="GameCode">The code of the game whose type to check.</param>
		protected void BackgroundGetGameType(string GameCode) {
			BackgroundWorker BW = new BackgroundWorker();
			_Workers.Add(BW);
			BW.DoWork             += DoBackgroundGetGameType;
			BW.RunWorkerCompleted += OnGetGameTypeCompleted;
			BW.WorkerReportsProgress      = false;
			BW.WorkerSupportsCancellation = true;
			BW.RunWorkerAsync(GameCode);
		}

		/// <summary>
		/// Fired when a call to BackgroundGetGameType completes.
		/// </summary>
		protected event RunWorkerCompletedEventHandler GetGameTypeCompleted;

		/// <summary>
		/// Handles the DoWork event for the BackgroundGetGameType worker.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work to be done.</param>
		private void DoBackgroundGetGameType(object Sender, DoWorkEventArgs e) {
			e.Result = GetGameType(e.Argument as string);
		}

		/// <summary>
		/// Fires the GetGameTypeCompleted event.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work which has completed.</param>
		private void OnGetGameTypeCompleted(object Sender, RunWorkerCompletedEventArgs e) {
			if (GetGameTypeCompleted != null) {
				GetGameTypeCompleted(this, e);
			}
			_Workers.Remove(Sender as BackgroundWorker);
		}

		/// <summary>
		/// Combines GetGameList and GetGameType to get the name and type of all games.
		/// </summary>
		/// <returns>A list of GameInfo structs, one for each game, and each containing only the game's code, name, and GameType.</returns>
		protected List<GameInfo> GetAllGameTypes() {
			// get a list of games
			List<GameInfo> GameList = GetGameList();

			// run through each game and get its type
			foreach (GameInfo Game in GameList) {
				Game.Type = GetGameType(Game.Code);
			}

			// return the list
			return GameList;
		}

		/// <summary>
		/// Used by BeginGetAllGameTypes and EndGetAllGameTypes.
		/// </summary>
		private delegate List<GameInfo> GetAllGameTypesDelegate();

		/// <summary>
		/// Starts an asynchronous call to GetAllGameTypes.
		/// </summary>
		/// <param name="Callback">A delegate to invoke when the asynchronous call completes.</param>
		/// <param name="AsyncState">State information to pass to the matching End call.</param>
		/// <returns>An IAsyncResult that can be used to monitor the asynchronous call.</returns>
		protected IAsyncResult BeginGetAllGameTypes(AsyncCallback Callback, object AsyncState) {
			return new GetAllGameTypesDelegate(GetAllGameTypes).BeginInvoke(Callback, AsyncState);
		}

		/// <summary>
		/// Ends an asynchronous call to GetAllGameTypes.
		/// </summary>
		/// <param name="Result">The return value from the matching Begin call.</param>
		/// <returns>A list of GameInfo structs, one for each game, and each containing only the game's code, name, and GameType.</returns>
		protected List<GameInfo> EndGetAllGameTypes(IAsyncResult Result) {
			return ((Result as AsyncResult).AsyncDelegate as GetAllGameTypesDelegate).EndInvoke(Result);
		}

		/// <summary>
		/// Starts a background call to GetAllGameTypes.  GetAllGameTypesCompleted will be fired on completion.
		/// </summary>
		protected void BackgroundGetAllGameTypes() {
			BackgroundWorker BW = new BackgroundWorker();
			_Workers.Add(BW);
			BW.DoWork             += DoBackgroundGetAllGameTypes;
			BW.RunWorkerCompleted += OnGetAllGameTypesCompleted;
			BW.WorkerReportsProgress      = false;
			BW.WorkerSupportsCancellation = true;
			BW.RunWorkerAsync();
		}

		/// <summary>
		/// Fired when a call to BackgroundGetAllGameTypes completes.
		/// </summary>
		protected event RunWorkerCompletedEventHandler GetAllGameTypesCompleted;

		/// <summary>
		/// Handles the DoWork event for the BackgroundGetAllGameTypes worker.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work to be done.</param>
		private void DoBackgroundGetAllGameTypes(object Sender, DoWorkEventArgs e) {
			e.Result = GetAllGameTypes();
		}

		/// <summary>
		/// Fires the GetAllGameTypesCompleted event.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work which has completed.</param>
		private void OnGetAllGameTypesCompleted(object Sender, RunWorkerCompletedEventArgs e) {
			if (GetAllGameTypesCompleted != null) {
				GetAllGameTypesCompleted(this, e);
			}
			_Workers.Remove(Sender as BackgroundWorker);
		}

		/// <summary>
		/// Uses the "F" command to check the user's subscription level to the given game.
		/// </summary>
		/// <param name="GameCode">The code for the game to check the subscription level for.</param>
		/// <returns>The user's subscription level to the given game.</returns>
		protected Subscription GetSubscription(string GameCode) {
			string[] Values = SendCommand("F", GameCode);

			switch (Values[1]) {
				case "NORMAL":      return Subscription.Normal;
				case "PREMIUM":     return Subscription.Premium;
				case "NEW_TO_GAME": return Subscription.New;
				case "EXPIRED":     return Subscription.Expired;
				case "TRIAL":       return Subscription.Trial;
				case "UNKNOWN":     return Subscription.None;
				default:            throw new ArgumentException("Unknown game code.");
			}
		}

		/// <summary>
		/// Used by BeginGetSubscription and EndGetSubscription.
		/// </summary>
		private delegate Subscription GetSubscriptionDelegate(string GameCode);

		/// <summary>
		/// Starts an asynchronous call to GetSubscription.
		/// </summary>
		/// <param name="GameCode">The code for the game to check the subscription level for.</param>
		/// <param name="Callback">A delegate to invoke when the asynchronous call completes.</param>
		/// <param name="AsyncState">State information to pass to the matching End call.</param>
		/// <returns>An IAsyncResult that can be used to monitor the asynchronous call.</returns>
		protected IAsyncResult BeginGetSubscription(string GameCode, AsyncCallback Callback, object AsyncState) {
			return new GetSubscriptionDelegate(GetSubscription).BeginInvoke(GameCode, Callback, AsyncState);
		}

		/// <summary>
		/// Ends an asynchronous call to GetSubscription.
		/// </summary>
		/// <param name="Result">The return value from the matching Begin call.</param>
		/// <returns>The user's subscription level to the given game.</returns>
		protected Subscription EndGetSubscription(IAsyncResult Result) {
			return ((Result as AsyncResult).AsyncDelegate as GetSubscriptionDelegate).EndInvoke(Result);
		}

		/// <summary>
		/// Starts a background call to GetSubscription.  GetSubscriptionCompleted will be fired on completion.
		/// </summary>
		/// <param name="GameCode">The code for the game to check the subscription level for.</param>
		protected void BackgroundGetSubscription(string GameCode) {
			BackgroundWorker BW = new BackgroundWorker();
			_Workers.Add(BW);
			BW.DoWork             += DoBackgroundGetSubscription;
			BW.RunWorkerCompleted += OnGetSubscriptionCompleted;
			BW.WorkerReportsProgress      = false;
			BW.WorkerSupportsCancellation = true;
			BW.RunWorkerAsync(GameCode);
		}

		/// <summary>
		/// Fired when a call to BackgroundGetSubscription completes.
		/// </summary>
		protected event RunWorkerCompletedEventHandler GetSubscriptionCompleted;

		/// <summary>
		/// Handles the DoWork event for the BackgroundGetSubscription worker.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work to be done.</param>
		private void DoBackgroundGetSubscription(object Sender, DoWorkEventArgs e) {
			e.Result = GetSubscription(e.Argument as string);
		}

		/// <summary>
		/// Fires the GetSubscriptionCompleted event.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work which has completed.</param>
		private void OnGetSubscriptionCompleted(object Sender, RunWorkerCompletedEventArgs e) {
			if (GetSubscriptionCompleted != null) {
				GetSubscriptionCompleted(this, e);
			}
			_Workers.Remove(Sender as BackgroundWorker);
		}

		/// <summary>
		/// Uses the "G" command to select a game on the server.
		/// </summary>
		/// <param name="GameCode">The code for the game to select on the server.</param>
		/// <returns>A GameInfo struct containing the game's code and relevent web sites.</returns>
		protected GameInfo SelectGame(string GameCode) {
			// grab the web sites from the server
			string[] Values = SendCommand("G", GameCode);

			// loop over them and add them to a site list
			Dictionary<string, string> Sites = new Dictionary<string, string>(Values.Length-5);
			for (int i=5; i<Values.Length; ++i) {
				string[] Pair = Values[i].Split('=');
				Sites.Add(Pair[0], Pair[1]);
			}

			// return the list
			return new GameInfo(GameCode, Sites);
		}

		/// <summary>
		/// Used by BeginSelectGame and EndSelectGame.
		/// </summary>
		private delegate GameInfo SelectGameDelegate(string GameCode);

		/// <summary>
		/// Starts an asynchronous call to SelectGame.
		/// </summary>
		/// <param name="GameCode">The code for the game to retrieve relevent web sites about.</param>
		/// <param name="Callback">A delegate to invoke when the asynchronous call completes.</param>
		/// <param name="AsyncState">State information to pass to the matching End call.</param>
		/// <returns>An IAsyncResult that can be used to monitor the asynchronous call.</returns>
		protected IAsyncResult BeginSelectGame(string GameCode, AsyncCallback Callback, object AsyncState) {
			return new SelectGameDelegate(SelectGame).BeginInvoke(GameCode, Callback, AsyncState);
		}

		/// <summary>
		/// Ends an asynchronous call to SelectGame.
		/// </summary>
		/// <param name="Result">The return value from the matching Begin call.</param>
		/// <returns>A GameInfo struct containing the game's code and relevent web sites.</returns>
		protected GameInfo EndSelectGame(IAsyncResult Result) {
			return ((Result as AsyncResult).AsyncDelegate as SelectGameDelegate).EndInvoke(Result);
		}

		/// <summary>
		/// Starts a background call to SelectGame.  SelectGameCompleted will be fired on completion.
		/// </summary>
		/// <param name="GameCode">The code for the game to retrieve relevent web sites about.</param>
		protected void BackgroundSelectGame(string GameCode) {
			BackgroundWorker BW = new BackgroundWorker();
			_Workers.Add(BW);
			BW.DoWork             += DoBackgroundSelectGame;
			BW.RunWorkerCompleted += OnSelectGameCompleted;
			BW.WorkerReportsProgress      = false;
			BW.WorkerSupportsCancellation = true;
			BW.RunWorkerAsync(GameCode);
		}

		/// <summary>
		/// Fired when a call to BackgroundSelectGame completes.
		/// </summary>
		protected event RunWorkerCompletedEventHandler SelectGameCompleted;

		/// <summary>
		/// Handles the DoWork event for the BackgroundSelectGame worker.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work to be done.</param>
		private void DoBackgroundSelectGame(object Sender, DoWorkEventArgs e) {
			e.Result = SelectGame(e.Argument as string);
		}

		/// <summary>
		/// Fires the SelectGameCompleted event.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work which has completed.</param>
		private void OnSelectGameCompleted(object Sender, RunWorkerCompletedEventArgs e) {
			if (SelectGameCompleted != null) {
				SelectGameCompleted(this, e);
			}
			_Workers.Remove(Sender as BackgroundWorker);
		}
/*
		/// <summary>
		/// Encapsulates GetGameType, GetSubscription, and GetWebSites into a single method.
		/// </summary>
		/// <param name="GameCode">The GameCode to look up information about.</param>
		/// <returns>A GameInfo struct that includes everything except the game's name.</returns>
		protected GameInfo GetGameInfo(string GameCode) {
			return new GameInfo(GameCode, GetGameType(GameCode), GetSubscription(GameCode), GetWebSites(GameCode));
		}

		/// <summary>
		/// Used by BeginGetGameInfo and EndGetGameInfo.
		/// </summary>
		private delegate GameInfo GetGameInfoDelegate(string GameCode);

		/// <summary>
		/// Starts an asynchronous call to GetGameInfo.
		/// </summary>
		/// <param name="GameCode">The GameCode to look up information about.</param>
		/// <param name="Callback">A delegate to invoke when the asynchronous call completes.</param>
		/// <param name="AsyncState">State information to pass to the matching End call.</param>
		/// <returns>An IAsyncResult that can be used to monitor the asynchronous call.</returns>
		protected IAsyncResult BeginGetGameInfo(string GameCode, AsyncCallback Callback, object AsyncState) {
			return new GetGameInfoDelegate(GetGameInfo).BeginInvoke(GameCode, Callback, AsyncState);
		}

		/// <summary>
		/// Ends an asynchronous call to GetGameInfo.
		/// </summary>
		/// <param name="Result">The return value from the matching Begin call.</param>
		/// <returns>A GameInfo struct that includes everything except the game's name.</returns>
		protected GameInfo EndGetGameInfo(IAsyncResult Result) {
			return ((Result as AsyncResult).AsyncDelegate as GetGameInfoDelegate).EndInvoke(Result);
		}

		/// <summary>
		/// Starts a background call to GetGameInfo.  GetGameInfoCompleted will be fired on completion.
		/// </summary>
		/// <param name="GameCode">The GameCode to look up information about.</param>
		protected void BackgroundGetGameInfo(string GameCode) {
			BackgroundWorker BW = new BackgroundWorker();
			_Workers.Add(BW);
			BW.DoWork             += DoBackgroundGetGameInfo;
			BW.RunWorkerCompleted += OnGetGameInfoCompleted;
			BW.WorkerReportsProgress      = false;
			BW.WorkerSupportsCancellation = true;
			BW.RunWorkerAsync(GameCode);
		}

		/// <summary>
		/// Fired when a call to BackgroundGetGameInfo completes.
		/// </summary>
		protected event RunWorkerCompletedEventHandler GetGameInfoCompleted;

		/// <summary>
		/// Handles the DoWork event for the BackgroundGetGameInfo worker.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work to be done.</param>
		private void DoBackgroundGetGameInfo(object Sender, DoWorkEventArgs e) {
			e.Result = GetGameInfo(e.Argument as string);
		}

		/// <summary>
		/// Fires the GetGameInfoCompleted event.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work which has completed.</param>
		private void OnGetGameInfoCompleted(object Sender, RunWorkerCompletedEventArgs e) {
			if (GetGameInfoCompleted != null) {
				GetGameInfoCompleted(this, e);
			}
			_Workers.Remove(Sender as BackgroundWorker);
		}

		/// <summary>
		/// Combines GetGameList with GetGameInfo to retrieve a GameInfo struct about all games which match the given type.
		/// </summary>
		/// <param name="Type">The type of game to look up information about.  Several types can be combined using bitwise OR.</param>
		/// <returns>A list of GameInfo structs, one for each game that matches the given type.</returns>
		protected List<GameInfo> GetAllGameInfo(GameType Type) {
			List<GameInfo> Games = GetGameList();
			foreach (GameInfo Game in Games) {
				if ((GetGameType(Game.Code) & Type) == Type) {
					Game.Merge(GetGameInfo(Game.Code));
				}
			}
			return Games;
		}

		/// <summary>
		/// Used by BeginGetAllGameInfo and EndGetAllGameInfo.
		/// </summary>
		private delegate List<GameInfo> GetAllGameInfoDelegate(GameType Type);

		/// <summary>
		/// Starts an asynchronous call to GetAllGameInfo.
		/// </summary>
		/// <param name="Type">The type of game to look up information about.</param>
		/// <param name="Callback">A delegate to invoke when the asynchronous call completes.</param>
		/// <param name="AsyncState">State information to pass to the matching End call.</param>
		/// <returns>An IAsyncResult that can be used to monitor the asynchronous call.</returns>
		protected IAsyncResult BeginGetAllGameInfo(GameType Type, AsyncCallback Callback, object AsyncState) {
			return new GetAllGameInfoDelegate(GetAllGameInfo).BeginInvoke(Type, Callback, AsyncState);
		}

		/// <summary>
		/// Ends an asynchronous call to GetAllGameInfo.
		/// </summary>
		/// <param name="Result">The return value from the matching Begin call.</param>
		/// <returns>A list of GameInfo structs, one for each game that matches the given type.</returns>
		protected List<GameInfo> EndGetAllGameInfo(IAsyncResult Result) {
			return ((Result as AsyncResult).AsyncDelegate as GetAllGameInfoDelegate).EndInvoke(Result);
		}

		/// <summary>
		/// Starts a background call to GetAllGameInfo.  GetAllGameInfoCompleted will be fired on completion.
		/// </summary>
		/// <param name="Type">The type of game to look up information about.</param>
		protected void BackgroundGetAllGameInfo(GameType Type) {
			BackgroundWorker BW = new BackgroundWorker();
			_Workers.Add(BW);
			BW.DoWork             += DoBackgroundGetAllGameInfo;
			BW.RunWorkerCompleted += OnGetAllGameInfoCompleted;
			BW.WorkerReportsProgress      = false;
			BW.WorkerSupportsCancellation = true;
			BW.RunWorkerAsync(Type);
		}

		/// <summary>
		/// Fired when a call to BackgroundGetAllGameInfo completes.
		/// </summary>
		protected event RunWorkerCompletedEventHandler GetAllGameInfoCompleted;

		/// <summary>
		/// Handles the DoWork event for the BackgroundGetAllGameInfo worker.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work to be done.</param>
		private void DoBackgroundGetAllGameInfo(object Sender, DoWorkEventArgs e) {
			e.Result = GetAllGameInfo((GameType)e.Argument);
		}

		/// <summary>
		/// Fires the GetAllGameInfoCompleted event.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work which has completed.</param>
		private void OnGetAllGameInfoCompleted(object Sender, RunWorkerCompletedEventArgs e) {
			if (GetAllGameInfoCompleted != null) {
				GetAllGameInfoCompleted(this, e);
			}
			_Workers.Remove(Sender as BackgroundWorker);
		}

		/// <summary>
		/// Uses the "P" command to "select" the given game for use on the server.
		/// </summary>
		/// <param name="GameCode">The game to select on the server.</param>
		/// <returns>The data returned by the server, not sure what it means.</returns>
		protected string[] SelectGame(string GameCode) {
			return SendCommand("P", GameCode);
		}

		/// <summary>
		/// Used by BeginSelectGame and EndSelectGame.
		/// </summary>
		private delegate string[] SelectGameDelegate(string GameCode);

		/// <summary>
		/// Starts an asynchronous call to SelectGame.
		/// </summary>
		/// <param name="GameCode">The game to select on the server.</param>
		/// <param name="Callback">A delegate to invoke when the asynchronous call completes.</param>
		/// <param name="AsyncState">State information to pass to the matching End call.</param>
		/// <returns>An IAsyncResult that can be used to monitor the asynchronous call.</returns>
		protected IAsyncResult BeginSelectGame(string GameCode, AsyncCallback Callback, object AsyncState) {
			return new SelectGameDelegate(SelectGame).BeginInvoke(GameCode, Callback, AsyncState);
		}

		/// <summary>
		/// Ends an asynchronous call to SelectGame.
		/// </summary>
		/// <param name="Result">The return value from the matching Begin call.</param>
		/// <returns>The data returned by the server, not sure what it means.</returns>
		protected string[] EndSelectGame(IAsyncResult Result) {
			return ((Result as AsyncResult).AsyncDelegate as SelectGameDelegate).EndInvoke(Result);
		}

		/// <summary>
		/// Starts a background call to SelectGame.  SelectGameCompleted will be fired on completion.
		/// </summary>
		/// <param name="GameCode">The game to select on the server.</param>
		protected void BackgroundSelectGame(string GameCode) {
			BackgroundWorker BW = new BackgroundWorker();
			_Workers.Add(BW);
			BW.DoWork             += DoBackgroundSelectGame;
			BW.RunWorkerCompleted += OnSelectGameCompleted;
			BW.WorkerReportsProgress      = false;
			BW.WorkerSupportsCancellation = true;
			BW.RunWorkerAsync(GameCode);
		}

		/// <summary>
		/// Fired when a call to BackgroundSelectGame completes.
		/// </summary>
		protected event RunWorkerCompletedEventHandler SelectGameCompleted;

		/// <summary>
		/// Handles the DoWork event for the BackgroundSelectGame worker.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work to be done.</param>
		private void DoBackgroundSelectGame(object Sender, DoWorkEventArgs e) {
			e.Result = SelectGame(e.Argument as string);
		}

		/// <summary>
		/// Fires the SelectGameCompleted event.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work which has completed.</param>
		private void OnSelectGameCompleted(object Sender, RunWorkerCompletedEventArgs e) {
			if (SelectGameCompleted != null) {
				SelectGameCompleted(this, e);
			}
			_Workers.Remove(Sender as BackgroundWorker);
		}
*/
		/// <summary>
		/// Uses the "C" command to retrieve a list of characters for the selected game from the server.
		/// </summary>
		/// <returns>A CharacterList struct containing the list of characters and some other information.</returns>
		protected CharacterList GetCharacterList() {
			// grab the values from the server
			string[] Values = SendCommand("C");

			// run through the characters and add them to a dictionary
			Dictionary<string,string> Characters = new Dictionary<string,string>((Values.Length-5)/2);
			for (int i=5; i<Values.Length; i+=2) {
				Characters.Add(Values[i], Values[i+1]);
			}

			// return a new struct
			return new CharacterList(int.Parse(Values[1]), int.Parse(Values[2]), (Values[3] == "1" ? true : false), (Values[4] == "1" ? true : false), Characters);
		}

		/// <summary>
		/// Used by BeginGetCharacterList and EndGetCharacterList.
		/// </summary>
		private delegate CharacterList GetCharacterListDelegate();

		/// <summary>
		/// Starts an asynchronous call to GetCharacterList.
		/// </summary>
		/// <param name="Callback">A delegate to invoke when the asynchronous call completes.</param>
		/// <param name="AsyncState">State information to pass to the matching End call.</param>
		/// <returns>An IAsyncResult that can be used to monitor the asynchronous call.</returns>
		protected IAsyncResult BeginGetCharacterList(AsyncCallback Callback, object AsyncState) {
			return new GetCharacterListDelegate(GetCharacterList).BeginInvoke(Callback, AsyncState);
		}

		/// <summary>
		/// Ends an asynchronous call to GetCharacterList.
		/// </summary>
		/// <param name="Result">The return value from the matching Begin call.</param>
		/// <returns>A CharacterList struct containing the list of characters and some other information.</returns>
		protected CharacterList EndGetCharacterList(IAsyncResult Result) {
			return ((Result as AsyncResult).AsyncDelegate as GetCharacterListDelegate).EndInvoke(Result);
		}

		/// <summary>
		/// Starts a background call to GetCharacterList.  GetCharacterListCompleted will be fired on completion.
		/// </summary>
		protected void BackgroundGetCharacterList() {
			BackgroundWorker BW = new BackgroundWorker();
			_Workers.Add(BW);
			BW.DoWork             += DoBackgroundGetCharacterList;
			BW.RunWorkerCompleted += OnGetCharacterListCompleted;
			BW.WorkerReportsProgress      = false;
			BW.WorkerSupportsCancellation = true;
			BW.RunWorkerAsync();
		}

		/// <summary>
		/// Fired when a call to BackgroundGetCharacterList completes.
		/// </summary>
		protected event RunWorkerCompletedEventHandler GetCharacterListCompleted;

		/// <summary>
		/// Handles the DoWork event for the BackgroundGetCharacterList worker.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work to be done.</param>
		private void DoBackgroundGetCharacterList(object Sender, DoWorkEventArgs e) {
			e.Result = GetCharacterList();
		}

		/// <summary>
		/// Fires the GetCharacterListCompleted event.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work which has completed.</param>
		private void OnGetCharacterListCompleted(object Sender, RunWorkerCompletedEventArgs e) {
			if (GetCharacterListCompleted != null) {
				GetCharacterListCompleted(this, e);
			}
			_Workers.Remove(Sender as BackgroundWorker);
		}

		/// <summary>
		/// Uses the "L" command to login and retrieve an SalFile.
		/// </summary>
		/// <param name="CharacterCode">The code for the character to play.</param>
		/// <param name="CharacterName">The name of the character to play.</param>
		/// <param name="Client">The client software to use.</param>
		/// <returns>An SalFile as returned from the login server.</returns>
		protected SalFile GetSalFile(string CharacterCode, string CharacterName, FrontEnd Client) {
			string ClientCode = (Client == FrontEnd.StormFront ? "STORM" : "PLAY");
			string[] Values = SendCommand("L", CharacterCode, ClientCode);
			return new SalFile(CharacterName, string.Join("\n", Values, 2, Values.Length-2));
		}

		/// <summary>
		/// Used by BeginGetSalFile and EndGetSalFile.
		/// </summary>
		private delegate SalFile GetSalFileDelegate(string CharacterCode, string CharacterName, FrontEnd Client);

		/// <summary>
		/// Starts an asynchronous call to GetSalFile.
		/// </summary>
		/// <param name="CharacterCode">The code for the character to play.</param>
		/// <param name="CharacterName">The name of the character to play.</param>
		/// <param name="Client">The client software to use.</param>
		/// <param name="Callback">A delegate to invoke when the asynchronous call completes.</param>
		/// <param name="AsyncState">State information to pass to the matching End call.</param>
		/// <returns>An IAsyncResult that can be used to monitor the asynchronous call.</returns>
		protected IAsyncResult BeginGetSalFile(string CharacterCode, string CharacterName, FrontEnd Client, AsyncCallback Callback, object AsyncState) {
			return new GetSalFileDelegate(GetSalFile).BeginInvoke(CharacterCode, CharacterName, Client, Callback, AsyncState);
		}

		/// <summary>
		/// Ends an asynchronous call to GetSalFile.
		/// </summary>
		/// <param name="Result">The return value from the matching Begin call.</param>
		/// <returns>An SalFile as returned from the login server.</returns>
		protected SalFile EndGetSalFile(IAsyncResult Result) {
			return ((Result as AsyncResult).AsyncDelegate as GetSalFileDelegate).EndInvoke(Result);
		}

		/// <summary>
		/// Starts a background call to GetSalFile.  GetSalFileCompleted will be fired on completion.
		/// </summary>
		/// <param name="CharacterCode">The code for the character to play.</param>
		/// <param name="CharacterName">The name of the character to play.</param>
		/// <param name="Client">The client software to use.</param>
		protected void BackgroundGetSalFile(string CharacterCode, string CharacterName, FrontEnd Client) {
			BackgroundWorker BW = new BackgroundWorker();
			_Workers.Add(BW);
			BW.DoWork             += DoBackgroundGetSalFile;
			BW.RunWorkerCompleted += OnGetSalFileCompleted;
			BW.WorkerReportsProgress      = false;
			BW.WorkerSupportsCancellation = true;
			BW.RunWorkerAsync(new object[]{CharacterCode, CharacterName, Client});
		}

		/// <summary>
		/// Fired when a call to BackgroundGetSalFile completes.
		/// </summary>
		protected event RunWorkerCompletedEventHandler GetSalFileCompleted;

		/// <summary>
		/// Handles the DoWork event for the BackgroundGetSalFile worker.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work to be done.</param>
		private void DoBackgroundGetSalFile(object Sender, DoWorkEventArgs e) {
			object[] Args = e.Argument as object[];
			e.Result = GetSalFile(Args[0] as string, Args[1] as string, (FrontEnd)Args[2]);
		}

		/// <summary>
		/// Fires the GetSalFileCompleted event.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work which has completed.</param>
		private void OnGetSalFileCompleted(object Sender, RunWorkerCompletedEventArgs e) {
			if (GetSalFileCompleted != null) {
				GetSalFileCompleted(this, e);
			}
			_Workers.Remove(Sender as BackgroundWorker);
		}

		/// <summary>
		/// Disconnects from the login server.
		/// </summary>
		protected void Disconnect() {
			_Connection.Disconnect();
		}

		/// <summary>
		/// Used by BeginDisconnect and EndDisconnect.
		/// </summary>
		private delegate void DisconnectDelegate();

		/// <summary>
		/// Starts an asynchronous call to Disconnect.
		/// </summary>
		/// <param name="Callback">A delegate to invoke when the asynchronous call completes.</param>
		/// <param name="AsyncState">State information to pass to the matching End call.</param>
		/// <returns>An IAsyncResult that can be used to monitor the asynchronous call.</returns>
		protected IAsyncResult BeginDisconnect(AsyncCallback Callback, object AsyncState) {
			return new DisconnectDelegate(Disconnect).BeginInvoke(Callback, AsyncState);
		}

		/// <summary>
		/// Ends an asynchronous call to Disconnect.
		/// </summary>
		/// <param name="Result">The return value from the matching Begin call.</param>
		protected void EndDisconnect(IAsyncResult Result) {
			((Result as AsyncResult).AsyncDelegate as DisconnectDelegate).EndInvoke(Result);
		}

		/// <summary>
		/// Starts a background call to Disconnect.  DisconnectCompleted will be fired on completion.
		/// </summary>
		protected void BackgroundDisconnect() {
			BackgroundWorker BW = new BackgroundWorker();
			_Workers.Add(BW);
			BW.DoWork             += DoBackgroundDisconnect;
			BW.RunWorkerCompleted += OnDisconnectCompleted;
			BW.WorkerReportsProgress      = false;
			BW.WorkerSupportsCancellation = true;
			BW.RunWorkerAsync();
		}

		/// <summary>
		/// Fired when a call to BackgroundDisconnect completes.
		/// </summary>
		protected event RunWorkerCompletedEventHandler DisconnectCompleted;

		/// <summary>
		/// Handles the DoWork event for the BackgroundDisconnect worker.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work to be done.</param>
		private void DoBackgroundDisconnect(object Sender, DoWorkEventArgs e) {
			Disconnect();
		}

		/// <summary>
		/// Fires the DisconnectCompleted event.
		/// </summary>
		/// <param name="Sender">The BackgroundWorker causing the event.</param>
		/// <param name="e">Arguments about the work which has completed.</param>
		private void OnDisconnectCompleted(object Sender, RunWorkerCompletedEventArgs e) {
			if (DisconnectCompleted != null) {
				DisconnectCompleted(this, e);
			}
			_Workers.Remove(Sender as BackgroundWorker);
		}

		/// <summary>
		/// Fired whenever the Module has a Message for Black Lightning.
		/// </summary>
		public event SendMessageEventHandler SendMessage;

		/// <summary>
		/// Fires the SendMessage event.
		/// </summary>
		/// <param name="e">Arguments to pass with the SendMessage event.</param>
		protected void OnSendMessage(SendMessageEventArgs e) {
			if (SendMessage != null) {
				SendMessage(this, e);
			}
		}

		/// <summary>
		/// Wraps the given Message in SendMessageEventArgs and fires the SendMessage event.
		/// </summary>
		/// <param name="M">The Message to send.</param>
		protected void Send(Message M) {
			OnSendMessage(new SendMessageEventArgs(M));
		}

		/// <summary>
		/// Creates a RegisterOptionMessage with the given parameters and sends it.
		/// </summary>
		/// <param name="Name">The name of the option.</param>
		/// <param name="ValueType">The type of the option's values.</param>
		/// <param name="DefaultValue">The option's default value.</param>
		protected void RegisterOption(string Name, Type ValueType, object DefaultValue) {
			Send(new RegisterOptionMessage(Name, ValueType, DefaultValue));
		}

		/// <summary>
		/// Creates a RegisterOptionMessage with the given parameters and sends it.
		/// </summary>
		/// <param name="Name">The name of the option.</param>
		/// <param name="ValueType">The type of the option's values.</param>
		/// <param name="DefaultValue">The option's default value.</param>
		/// <param name="Description">The option's description.</param>
		protected void RegisterOption(string Name, Type ValueType, object DefaultValue, string Description) {
			Send(new RegisterOptionMessage(Name, ValueType, DefaultValue, Description));
		}

		/// <summary>
		/// Creates a RegisterOptionMessage with the given parameters and sends it.
		/// </summary>
		/// <param name="Name">The name of the option.</param>
		/// <param name="ValueType">The type of the option's values.</param>
		/// <param name="DefaultValue">The option's default value.</param>
		/// <param name="Description">The option's description.</param>
		/// <param name="Category">The name of the option's category.</param>
		protected void RegisterOption(string Name, Type ValueType, object DefaultValue, string Description, string Category) {
			Send(new RegisterOptionMessage(Name, ValueType, DefaultValue, Description, Category));
		}

		/// <summary>
		/// Creates a RegisterOptionMessage with the given parameters and sends it.
		/// </summary>
		/// <param name="Name">The name of the option.</param>
		/// <param name="ValueType">The type of the option's values.</param>
		/// <param name="DefaultValue">The option's default value.</param>
		/// <param name="Description">The option's description.</param>
		/// <param name="Category">The name of the option's category.</param>
		/// <param name="Hidden">Whether or not the option is hidden from the user.</param>
		protected void RegisterOption(string Name, Type ValueType, object DefaultValue, string Description, string Category, bool Hidden) {
			Send(new RegisterOptionMessage(Name, ValueType, DefaultValue, Description, Category, Hidden));
		}

		/// <summary>
		/// Creates a RegisterOptionMessage with the given parameters and sends it.
		/// </summary>
		/// <param name="Name">The name of the option.</param>
		/// <param name="ValueType">The type of the option's values.</param>
		/// <param name="DefaultValue">The option's default value.</param>
		/// <param name="Description">The option's description.</param>
		/// <param name="Category">The name of the option's category.</param>
		/// <param name="Hidden">Whether or not the option is hidden from the user.</param>
		/// <param name="ReadOnly">Whether or not the option is read only.</param>
		protected void RegisterOption(string Name, Type ValueType, object DefaultValue, string Description, string Category, bool Hidden, bool ReadOnly) {
			Send(new RegisterOptionMessage(Name, ValueType, DefaultValue, Description, Category, Hidden, ReadOnly));
		}

		/// <summary>
		/// Creates a RegisterOptionMessage with the given parameters and sends it.
		/// </summary>
		/// <param name="Name">The name of the option.</param>
		/// <param name="ValueType">The type of the option's values.</param>
		/// <param name="DefaultValue">The option's default value.</param>
		/// <param name="Description">The option's description.</param>
		/// <param name="Category">The name of the option's category.</param>
		/// <param name="Hidden">Whether or not the option is hidden from the user.</param>
		/// <param name="ReadOnly">Whether or not the option is read only.</param>
		/// <param name="EncryptKey">The key to use to encrypt and decrypt the option's value while stored on disk, or null to not encrypt the value.  Only used if ValueType == typeof(string).</param>
		protected void RegisterOption(string Name, Type ValueType, object DefaultValue, string Description, string Category, bool Hidden, bool ReadOnly, string EncryptKey) {
			Send(new RegisterOptionMessage(Name, ValueType, DefaultValue, Description, Category, Hidden, ReadOnly, EncryptKey));
		}

		/// <summary>
		/// Creates a RegisterOptionMessage with the given parameters and sends it.
		/// </summary>
		/// <param name="Name">The name of the option.</param>
		/// <param name="ValueType">The type of the option's values.</param>
		/// <param name="DefaultValue">The option's default value.</param>
		/// <param name="Description">The option's description.</param>
		/// <param name="Category">The name of the option's category.</param>
		/// <param name="Hidden">Whether or not the option is hidden from the user.</param>
		/// <param name="ReadOnly">Whether or not the option is read only.</param>
		/// <param name="EncryptKey">The key to use to encrypt and decrypt the option's value while stored on disk, or null to not encrypt the value.  Only used if ValueType == typeof(string).</param>
		/// <param name="Editor">The UITypeEditor-derived type to use as a UI editor for the option's type.</param>
		protected void RegisterOption(string Name, Type ValueType, object DefaultValue, string Description, string Category, bool Hidden, bool ReadOnly, string EncryptKey, Type Editor) {
			Send(new RegisterOptionMessage(Name, ValueType, DefaultValue, Description, Category, Hidden, ReadOnly, EncryptKey, Editor));
		}

		/// <summary>
		/// Creates a RegisterOptionMessage with the given parameters and sends it.
		/// </summary>
		/// <param name="Name">The name of the option.</param>
		/// <param name="ValueType">The type of the option's values.</param>
		/// <param name="DefaultValue">The option's default value.</param>
		/// <param name="Description">The option's description.</param>
		/// <param name="Category">The name of the option's category.</param>
		/// <param name="Hidden">Whether or not the option is hidden from the user.</param>
		/// <param name="ReadOnly">Whether or not the option is read only.</param>
		/// <param name="EncryptKey">The key to use to encrypt and decrypt the option's value while stored on disk, or null to not encrypt the value.  Only used if ValueType == typeof(string).</param>
		/// <param name="Editor">The UITypeEditor-derived type to use as a UI editor for the option's type.</param>
		/// <param name="Converter">The TypeConverter-derived type to use to convert the option's values to and from strings.</param>
		protected void RegisterOption(string Name, Type ValueType, object DefaultValue, string Description, string Category, bool Hidden, bool ReadOnly, string EncryptKey, Type Editor, Type Converter) {
			Send(new RegisterOptionMessage(Name, ValueType, DefaultValue, Description, Category, Hidden, ReadOnly, EncryptKey, Editor, Converter));
		}

		/// <summary>
		/// Wraps the given name in an UnregisterOptionMessage and sends it.
		/// </summary>
		/// <param name="Name">The name of the option to unregister.</param>
		protected void UnregisterOption(string Name) {
			Send(new UnregisterOptionMessage(Name));
		}

		/// <summary>
		/// Wraps the given name and value in a ChangeOptionValueMessage and sends it.
		/// </summary>
		/// <param name="Name">The name of the option to change the value of.</param>
		/// <param name="Value">The value to change the option's value to.</param>
		protected void ChangeOptionValue(string Name, object Value) {
			Send(new ChangeOptionValueMessage(Name, Value));
		}

		/// <summary>
		/// Sends a ShowOptionsMessage to Black Lightning to display the options dialog.
		/// </summary>
		protected void ShowOptions() {
			Send(new ShowOptionsMessage());
		}

		/// <summary>
		/// Called when Black Lightning has a Message specifically for this Module.
		/// </summary>
		/// <param name="Message">The Message being delivered to the Module.</param>
		public abstract void HandleMessage(Message Message);

		/// <summary>
		/// Gets the value of a property of the login client.
		/// </summary>
		/// <param name="Property">The property to retrieve the value of.</param>
		/// <returns>The value of the given property, or null if the login client doesn't use that property.</returns>
		public abstract object GetProperty(LoginClientProperty Property);

		/// <summary>
		/// Provides the login logic for any specific implementation of LoginClient.
		/// </summary>
		/// <param name="Args">The command-line arguments passed to Black Lightning for use logging in.</param>
		/// <returns>An SalFile that can be used to login to a game and play.</returns>
		public abstract SalFile Login(string[] Args);
	}

	/// <summary>
	/// An enumeration of possible game types.
	/// </summary>
	[Flags]
	public enum GameType {
		/// <summary>
		/// The game in question is a production game.
		/// </summary>
		Production,

		/// <summary>
		/// The game in question is for development only.
		/// </summary>
		Development
	}

	/// <summary>
	/// An enumeration of all possible subscription levels to a game.
	/// </summary>
	public enum Subscription {
		/// <summary>
		/// No subscription.
		/// </summary>
		None,

		/// <summary>
		/// Standard subscription.
		/// </summary>
		Normal,

		/// <summary>
		/// Premium subscription.
		/// </summary>
		Premium,

		/// <summary>
		/// Subscription is brand new?
		/// </summary>
		New,

		/// <summary>
		/// Subscription has expired?
		/// </summary>
		Expired,

		/// <summary>
		/// On a 30-day trial subscription?  Trial subscription is available?
		/// </summary>
		Trial
	}

	/// <summary>
	/// Contains a set of information about a single game.  Different methods of LoginClient provide GameInfo structs populated with different information.
	/// </summary>
	/// <remarks>
	/// Since it's not possible to get all the information about a game at once from a LoginClient, the Merge function is provided to allow new information to be merged into older structs, allowing you to build up a game's information over time.
	/// </remarks>
	public class GameInfo {
		// member data
		private string                     _Code;         // the game's code
		private string                     _Name;         // the game's name
		private GameType?                  _Type;         // the game's GameType
		private Subscription?              _Subscription; // the user's subscription level to the game
		private Dictionary<string, string> _WebSites;     // a list of web sites relevent to the game

		/// <summary>
		/// Creates a new GameInfo struct.
		/// </summary>
		/// <param name="Code">The game's code.</param>
		/// <param name="Name">The game's name.</param>
		public GameInfo(string Code, string Name) {
			_Code         = Code;
			_Name         = Name;
			_Type         = null;
			_Subscription = null;
			_WebSites     = null;
		}
/*
		/// <summary>
		/// Creates a new GameInfo struct.
		/// </summary>
		/// <param name="Code">The game's code.</param>
		/// <param name="Name">The game's name.</param>
		/// <param name="Type">The game's GameType.</param>
		public GameInfo(string Code, string Name, GameType Type) {
			_Code         = Code;
			_Name         = Name;
			_Type         = Type;
			_Subscription = null;
			_WebSites     = null;
		}
*/
		/// <summary>
		/// Creates a new GameInfo struct.
		/// </summary>
		/// <param name="Code">The game's code.</param>
		/// <param name="WebSites">A list of web sites relevent for the game.</param>
		public GameInfo(string Code, Dictionary<string, string> WebSites) {
			_Code         = Code;
			_Name         = null;
			_Type         = null;
			_Subscription = null;
			_WebSites     = WebSites;
		}

		/// <summary>
		/// Gets the game's game code.
		/// </summary>
		public string Code {
			get {
				return _Code;
			}
			set {
				_Code = value;
			}
		}

		/// <summary>
		/// Gets the game's name.
		/// </summary>
		public string Name {
			get {
				return _Name;
			}
			set {
				_Name = value;
			}
		}

		/// <summary>
		/// Gets the game's GameType.
		/// </summary>
		public GameType? Type {
			get {
				return _Type;
			}
			set {
				_Type = value;
			}
		}

		/// <summary>
		/// Gets the user's subscription level to the game.
		/// </summary>
		public Subscription? Subscription {
			get {
				return _Subscription;
			}
			set {
				_Subscription = value;
			}
		}

		/// <summary>
		/// Gets a list of web sites which are relevent to the game.
		/// </summary>
		/// <remarks>
		/// Some URLs may contain the string "{KEY}" which should be replaced by the KEY value returned from Authenticate before being used.
		/// </remarks>
		public Dictionary<string, string> WebSites {
			get {
				return _WebSites;
			}
			set {
				_WebSites = value;
			}
		}

		/// <summary>
		/// Merges the information from the given GameInfo struct into this one.  No information is overwritten, only new information is copied.
		/// </summary>
		/// <param name="GI">The GameInfo struct to copy new information out of into this one.</param>
		public void Merge(GameInfo GI) {
			// if we don't have a code and the given one does, copy that over
			if (this._Code == null && GI._Code != null) {
				this._Code = GI._Code;
			}

			// if we don't have a name and the given one does, copy that over
			if (this._Name == null && GI._Name != null) {
				this._Name = GI._Name;
			}

			// if we don't have a subscription and the given one does, copy that over
			if (this._Subscription == null && GI._Subscription != null) {
				this._Subscription = GI._Subscription;
			}

			// if we don't have a name and the given one does, copy that over
			if (this._Type == null && GI._Type != null) {
				this._Type = GI._Type;
			}

			// if we don't have a web site list and the given one does, copy that over
			if (this._WebSites == null && GI._WebSites != null) {
				this._WebSites = GI._WebSites;
			}
		}
	}

	/// <summary>
	/// A list of characters and other information returned by GetCharacterList.
	/// </summary>
	public class CharacterList {
		// member data
		private int                       _SlotsUsed;  // the number of character slots in use
		private int                       _SlotsTotal; // the number of character slots on the account
		private bool                      _Unknown1;   // an unknown boolean value
		private bool                      _Unknown2;   // another unknown boolean value
		private Dictionary<string,string> _Characters; // the list of characters (maps IDs to names)

		/// <summary>
		/// Creates a new CharacterList struct.
		/// </summary>
		/// <param name="SlotsUsed">The number of character slots used on the account.</param>
		/// <param name="SlotsTotal">The total number of character slots on the account.</param>
		/// <param name="Unknown1">The first of two unknown boolean values.</param>
		/// <param name="Unknown2">The second of two unknown boolean values.</param>
		/// <param name="Characters">The list of characters (IDs mapped to names).</param>
		public CharacterList(int SlotsUsed, int SlotsTotal, bool Unknown1, bool Unknown2, Dictionary<string, string> Characters) {
			_SlotsUsed = SlotsUsed;
			_SlotsTotal = SlotsTotal;
			_Unknown1 = Unknown1;
			_Unknown2 = Unknown2;
			_Characters = Characters;
		}

		/// <summary>
		/// Gets the number of character slots used on the account.
		/// </summary>
		public int SlotsUsed {
			get {
				return _SlotsUsed;
			}
		}

		/// <summary>
		/// Gets the total number of character slots on the account.
		/// </summary>
		public int SlotsTotal {
			get {
				return _SlotsTotal;
			}
		}

		/// <summary>
		/// Gets the first of two unknown boolean values.
		/// </summary>
		public bool Unknown1 {
			get {
				return _Unknown1;
			}
		}

		/// <summary>
		/// Gets the second of two unknown boolean values.
		/// </summary>
		public bool Unknown2 {
			get {
				return _Unknown2;
			}
		}

		/// <summary>
		/// Gets a dictionary that maps each character's ID to their name.
		/// </summary>
		public Dictionary<string, string> Characters {
			get {
				return _Characters;
			}
		}
	}
}
